BigOrm.php.bak

<?php

namespace Tlf;

use PDO;

/**
 * Minimal ORM implementation.
 *
 *
 * Declare `$protected ClassName $_article;` and `public int $article_id;` to automagically make `$obj->article` load the related article as a BigOrm object.
 * @tagline (Alpha version) A minimalist ORM for mapping arrays of data to objects with magic getters & some convenience methods
 */
#[\AllowDynamicProperties]
class BigOrm {

    protected array $_cache = [];

    protected \Tlf\BigDb $bdb;
    protected string $table;

    public function __construct(\Tlf\BigDb $bdb, array $row = []){
        if (!isset($this->table)){
            $class = get_class($this);
            $parts = explode('\\', $class);
            $this->table = strtolower(array_pop($parts));
        }
        $this->bdb = $bdb;

        $row = $this->sanitize($row);
        foreach ($row as $k=>$v){
            $this->$k = $v;
        }

    }

    /**
     * A hook for sanitizing row data passed to `__construct()`. The built-in version of this method does nothing. 
     * 
     * @param $row a row of data that has not been sanitized
     * @return array of safe data 
     * @override Builtin version does nothing
     */
    public function sanitize(array $row): array {
        return $row;
    }
    /**
     * Called at end of `save()`, after the update/insert is finished AND id is set (in case of an INSERT)
     * @override 
     */
    public function didSave(){}

    /**
     * Store current object in database. 
     * UPDATE if `get_saveable_row()` returns an `id`. INSERT otherwise
     */
    public function save(){
        $row = [];
        $row = $this->get_saveable_row();

        if (isset($row['id'])){
            $this->bdb->ldb->update($this->table, $row, 'id');
        } else {
            $id = $this->bdb->ldb->insert($this->table, $row, 'id');
            $this->id = $id;
        }
        $this->didSave();
    }

    /**
     * Returns array of all dynamic properties and properties explicitly defined on the BigOrm subclass.
     * @override for custom saveable row
     * @return array of key/value pairs to store in the database
     */
    public function get_saveable_row(): array{
        $row = [];
        $obj_vars = array_keys(get_object_vars($this));
        $defined_properties = array_keys(get_class_vars(get_class($this)));
        $all_props = array_merge($obj_vars, $defined_properties);
        $bigorm_props = array_keys(get_class_vars(self::class));

        $storable = array_diff($all_props, $bigorm_props);
        foreach ($storable as $prop){
            if (substr($prop,0,1)=='_')continue;
            $row[$prop] = $this->$prop ?? null;
        }
        return $row;
    }

    /**
     * Create an object
     */
    public function object($name, array $row){
        $class = get_class($this);
        $parts = explode("\\",$class);
        array_pop($parts);
        $namespace = implode("\\", $parts);
        $class = $namespace.'\\'.ucfirst($name);
        if (class_exists($class)){
            return new $class($row);
        }

        return (object)$row;
    }

    /**
     * Get an array of objects from rows
     * @param $rows an array of rows
     * @return array of objects
     */
    static public function from_rows(array $rows){
        $list = [];
        foreach ($rows as $r){
            $class = static::class;
            $list[] = new $class($r);
        }
        return $list;
    }


    /**
     * Call `getPropName()` or magically load a related prop if property `_propName` is declared AND property `propName_id` is set
     */
    public function __get(string $prop){
        // return 'nothing';
        $value = $this->property_getter($prop);
        if ($value!==null)return $value;

        $value = $this->property_related($prop);
        if ($value!==null)return $value;


        // @todo probably throw an exception here
        return $value;
    }

    /**
     * Call `setPropName()` if the setter method exists. Otherwise set dynamic property. 
     * If the property is defined on the class & no setter is available, then an exception is thrown.
     */
    public function __set(string $prop, $value){
        $method = 'set'.ucfirst($prop);
        if (method_exists($this,$method)){
            $this->$method($value);
            return;
        }

        $class = get_class($this);
        if (property_exists($class,$prop)){
            throw new \Exception("Property '$prop' is not accessible on $class, and property setter '$method' does not exist.");
        }

        $this->$prop = $value;
    }

    public function property_related(string $prop): ?\Tlf\BigOrm {
        $magic_prop = "_".$prop;
        if (!property_exists(get_class($this),$magic_prop))return null;
        if ($this->$magic_prop!=null)return $this->$magic_prop;
        $id_prop = $prop.'_id';
        // $use_magic_loader = !isset($this->$magic_prop) && isset($this->$id_prop);
        //@todo maybe throw exception if the properties don't exist
        if (!isset($this->$id_prop))return null;

        $item = $this->bdb->one($prop, $this->$id_prop);
        $this->$magic_prop = $item;
        return $item;
    }

    public function property_getter($prop){
        if (substr($prop,0,1)=='_')return false;
        if (method_exists($this,$mthd='get'.ucfirst($prop))){
            return $this->$mthd();
        }

        return null;
    }
}